package com.batchengine.startup;

import static com.batchengine.util.LoggingFactory.createLogger;
import org.apache.log4j.Logger;

import java.io.*;
import java.net.Socket;

/**
 * Created by sunil.incubator@yahoo.com.
 * User: sunil.kalva
 * Date: Jul 11, 2009
 * <p/>
 * This class implements operations useful for creating a client of the DC Service. You can use the <tt>interact</tt>
 * method to start an interactive session on the DC Service's control port. You can use the <tt>match</tt> method to
 * implement scripting of commands.
 */
public class ServiceClient {

    /**
     * Fully qualified class name
     */
    private static final String FQCN = ServiceClient.class.getName();

    private static final Logger logger = createLogger();

    protected Socket socket;
    protected PrintWriter writer;
    protected BufferedReader reader;

    /**
     * Creates a <tt>ServiceClient</tt>.
     *
     * @param host the host name or IP address of the DC Service
     * @param port the control port of the DC Service
     * @throws java.io.IOException if an I/O error occurs
     */
    public ServiceClient(String host, int port) throws IOException {
        socket = new Socket(host, port);
        writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
        reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    }

    /**
     * This methods allows for simple scripting of commands. You can specify a string that you want the script to
     * look for, and when the string is matched, you can specify a command to send.
     *
     * @param expect        the string to expect
     * @param send          the string to send when the string that was expected is matched
     * @param sendEndOfLine Specifies whether to send End-Of-Line after sending the <tt>send</tt> string.
     * @throws ServiceClientException if the <tt>expect</tt> cannot be matched before we reached the end of the
     *                                input stream
     * @throws java.io.IOException    if an I/O error occurs
     */
    public void match(String expect, String send, boolean sendEndOfLine) throws IOException, ServiceClientException {

        StringBuffer buf = new StringBuffer();
        while (true) {
            int ch = reader.read();
            if (ch == -1) {
                break;
            }
            buf.append((char) ch);
            if (buf.toString().endsWith(expect)) {
                if (sendEndOfLine) {
                    writer.println(send);
                } else {
                    writer.print(send);
                }
                return;
            }
        }
        throw new ServiceClientException(String.format("Cannot match \"%s\" to send \"%s\".", expect, send));
    }

    /**
     * This method starts an interactive session (via <tt>System.in</tt> and <tt>System.out</tt> with the DC Service
     * on its control port. Note that this method exits the system (by calling <tt>System.exit</tt> when the server
     * terminates the connection.
     */
    public void interact() {
        Thread readThread = new Thread() {
            public void run() {
                try {
                    while (true) {
                        int ch = reader.read();
                        if (ch == -1) {
                            break;
                        }
                        logger.debug((char) ch);
                    }
                } catch (IOException ioe) {
                    // Thread should terminate when encountered an IOException.
                }
            }
        };

        Thread writeThread = new Thread() {
            public void run() {
                BufferedReader commandReader = new BufferedReader(new InputStreamReader(System.in));
                try {
                    while (true) {
                        String command = commandReader.readLine();
                        writer.println(command);
                        writer.flush();
                    }
                } catch (IOException ioe) {
                    // Exception can be ignored.
                }
            }
        };

        readThread.start();
        writeThread.start();

        try {
            readThread.join();
        } catch (InterruptedException e) {
        }

        // The readThread dies when the server closes its connection. We cannot really kill the writeThread. So we
        // will just force an exit here.
        try {
            close();
        } catch (IOException ioe) {
            // This exception can be ignored.
        } finally {
            System.exit(0);
        }
    }

    /**
     * Closes the connection to the DC Service.
     */
    public void close() throws IOException {
        socket.close();
    }
}
